# -*- coding: utf-8 -*-

#    Copyright (C) 2014 by Promotux
#                       di Francesco Meloni snc - http://www.promotux.it/

#    Author: Francesco Meloni  <francesco@promotux.it>

#    This file is part of OdCollect.

#    OdCollect is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 2 of the License, or
#    (at your option) any later version.

#    OdCollect is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.

#    You should have received a copy of the GNU General Public License
#    along with OdCollect.  If not, see <http://www.gnu.org/licenses/>.

import os
from threading import Timer
import hashlib
from calendar import Calendar
from decimal import *

import threading
import time
import datetime
from sqlalchemy.orm import *
from sqlalchemy import *
import Environment

import string
import re
import xml.etree.ElementTree as ET
import unicodedata
import urllib
import urllib2
try:
    import json
except:
    None


from reportlab.platypus import  Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.pagesizes import cm
from reportlab.graphics.barcode.common import *
from reportlab.graphics.barcode.usps import *
from reportlab.graphics import renderPM
from reportlab.graphics.barcode import createBarcodeDrawing
from reportlab.graphics.barcode import createBarcodeImageInMemory


def timeit(method):

    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()
        print '%r %r(%r, %r) %2.2f sec' % \
              (method.__module__,method.__name__, args, kw, te-ts)
        return result

    return timed


def emptyStringToNone(string):
    """
    Restituisce None se la stringa e' nulla
    """
    if string == '':
        return None
    else:
        return string


def prepareFilterString(string=None):
    """
    Tratta una stringa prima di essere passata come parametro in un filtro:
    Restituisce None se la stringa e' nulla e sostituisce un apice con la relativa sequenza di escape
    """
    if (string or '') == '':
        return ""
    else:
        return string.replace('\'', '\\\'')


def dateToString(data):
    """
    Converte una data in stringa con formato GG/MM/AAAA
    """
    if data is None:
        return ''
    elif type(data) == str:
        return data
    else:
        try:
            s = string.zfill(str(data.day),2) + '/' + string.zfill(str(data.month),2) + '/' + string.zfill(str(data.year),4)
        except Exception:
            s = ''
        return s


def stringToDate(stringa):
    """
    Converte una stringa con formato GG/MM/AAAA in data
    """
    if not stringa:
        return None
    else:
        try:
            d = time.strptime(stringa, "%d/%m/%Y")
        except ValueError:
            return None
        else:
            return datetime.date(d[0], d[1], d[2])


def stringToDateBumped(stringa, giorni=1):
    """
    Ritorna la data aumentata di uno o più giorni

    Arguments:
    - `stringa`: una data
    - `giorni`: il numero di giorni da aggiungere
    """
    stringa = stringToDate(stringa)
    if stringa:
        return stringa + datetime.timedelta(days=giorni)
    else:
        return None

def dateTimeToString(data):
    """
    Converte una data + ora in stringa
    """
    if data is None:
        return ''
    elif type(data) == str:
        return data
    else:
        try:
            if string.zfill(str(data.month),2) and string.zfill(str(data.minute),2) != "00":
                s = string.zfill(str(data.day), 2) +\
                '/' + string.zfill(str(data.month),2) +\
                '/' + string.zfill(str(data.year),4) + \
                ' ' + string.zfill(str(data.hour),2) + \
                ':' + string.zfill(str(data.minute),2)
            else:
                s = string.zfill(str(data.day), 2) +\
                '/' + string.zfill(str(data.month),2) +\
                '/' + string.zfill(str(data.year),4)
        except Exception:
            s = ''
        return s


def stringToDateTime(stringa):
    """
    Converte una stringa in data + ora
    """
    if stringa is None or stringa == '':
        return None
    else:
        try:
            d = time.strptime(stringa.strip(), "%d/%m/%Y %H:%M")
            data = datetime.datetime(d[0], d[1], d[2], d[3], d[4])
        except:
            try:
                d = time.strptime(stringa.strip(), "%d/%m/%Y")
                data = datetime.datetime(d[0], d[1], d[2])
#            messageInfo(msg= "LA DATA E' IN QUALCHE MODO ERRATA O INCOMPLETA")
            except:
                data=None
        return data

def controllaDateFestivi(data):
    """Controlla se una data è festiva

    Arguments:
    - `stringa`: la data da controllare
    """
    festivi = ['1508', '3112']
    if type (data) != str:
        data = data.strftime('%d%m')
    else:
        data = data[:4]
    return data in festivi

def get_data_mese_precedente(data=None):
    from datetime import timedelta
    if not data:
        data = datetime.datetime.utcnow()
    return data.replace(day=1) - timedelta(days=1)

def dataInizioFineMese(data):
    import calendar
    mese = data.month
    anno = data.year
    giorni = calendar.monthrange(anno, mese)
    return (datetime.date(anno, mese, 1), datetime.date(anno, mese, giorni[1]))


def setFileName(filename, ext, returnName = False):
    """
    Verify that the filename have the extension "ext"
    If not, it will append the extension to the end of the filename.
    """
    name = os.path.split(filename)
    _filename = os.path.splitext(name[1])
    _ext = _filename[1].upper()[1:]

    if _ext == ext.upper():
        if returnName:
            return name[1]
        else:
            return filename

    else:
        if returnName:
            return _filename[0]+'.'+ext.lower()
        else:
            _name = name[0]+os.path.sep+_filename[0]+'.'+ext.lower()
            return _name


def getDateRange(string):
    """
    returns a set of two timestamps one at beginning and at the end of
    the year indicated by string (it must be placed on the last 4 characters of the string) (01/01/YEAR, 31/12/YEAR)
    """
    capodanno = '01/01/'+string[-4:]
    san_silvestro = '31/12/'+string[-4:]
    begin_date = stringToDate(capodanno)
    end_date = stringToDate(san_silvestro)
    return (begin_date, end_date)

def destroy_event(window):
    """
    Send a 'destroy-event' to the specified gtk.Window
    """
    event = GDK_EVENT(GDK_EVENT_DESTROY)

    event.send_event = True
    event.window = window.window

    gtk.main_do_event(event)


def hasAction(actionID=None):
    """
    La modularizzazione richiede che
    quando il modulo non è presente o non è attivato
    la risposta sia sempre true perchè essendoci solo
    admin ha de facto tutti i privilegi
    """
    from promogest.modules.RuoliAzioni.dao.RoleAction import RoleAction
    idRole = Environment.params['usernameLoggedList'][2]
    if Environment.idACT == []:
        aa= RoleAction().select(id_role=idRole, batchSize=None)
        Environment.idACT = [x.id_action for x in aa]

    #roleActions = RoleAction().select(id_role=idRole,
                                            #id_action=actionID,
                                            #)
    if actionID in Environment.idACT: #roleActions:
        return True
    else:
        dialog = gtk.MessageDialog( None,
                                GTK_DIALOG_MODAL |
                                GTK_DIALOG_DESTROY_WITH_PARENT,
                                GTK_DIALOG_MESSAGE_WARNING, GTK_BUTTON_OK,
                                _("Permesso negato! L'azione richiesta non è tra quelle che ti son consentite"))
        dialog.run()
        dialog.destroy()
        return False


def getCategorieContatto(id=None):
    """
    """
    from promogest.dao.daoContatti.ContattoCategoriaContatto import ContattoCategoriaContatto
    dbCategorieContatto = ContattoCategoriaContatto().select(id=id,
                                                            batchSize=None)
    return dbCategorieContatto


def sanitizer(value):
    if value:
        value = value.strip()
        value = value.replace("€", "")
        value = value.replace("%", "")
        value = value.replace(",", ".")
    return value


def mN(value, decimal=None):
    """
    funzione importante perchè normalizza le valute, mettendo i decimali così
    come settato nel configure e restituisce un arrotondamento corretto
    RICORDA: Per un valore TOTALE quindi con due decimali si deve forzare il 2
    UPDATE: adesso supporta anche lo zero
    """

    if not value or value == '' or value == "None":
        return Decimal(0)
    #negativo = False
    #if "-" in str(value):
        #negativo = True
        #value = value*-1

    value = deItalianizza(value)
    if decimal >= 0:
        precisione = decimal
    else:
        precisione = int(setconf(key="decimals", section="Numbers"))
    if precisione == 0:
        decimals = 0
    else:
        decimals = Decimal(10) ** - (precisione)
    newvalue= Decimal(str(value).strip()).quantize(Decimal(decimals), rounding=ROUND_HALF_UP)
    #if negativo:
        #newvalue = newvalue*-1
    return newvalue


def mNLC(value,decimal=None, curr="€ "):
    """
    Per il momento ritorna mN, da implementare la localizzazione
    anche solo di valuta
    """
    value = mN(value, decimal=decimal)
    curr = setconf("Valuta", "valuta_curr")
    dp =","
    sep="."
    if curr =="$" or curr=="£":
        dp="."
        sep=","
    elif curr =="€":
        curr = u"€"
        dp=","
        sep="."
    return italianizza(value, decimal=decimal,curr=curr+" ", dp=dp, sep=sep)


def mNL(value,decimal=None):
    """
    Per il momento ritorna mN, da implementare la localizzazione
    anche solo di valuta
    """
    value = mN(value, decimal=decimal)
    return italianizza(value, decimal=decimal)


def deItalianizza(value):
    if type("stringa") == type(value):
        if "€" in str(value):
            value = value.replace("€","")
        if "," in str(value) and "." in str(value):
            value = value.replace(".", "")
            value = value.replace(",", ".")
        elif "," in str(value):
            value = value.replace(",", ".")
        value = value.replace(" ", "")
    return value


def italianizza(value, decimal=0, curr='', sep='.', dp=',',
             pos='', neg='-', trailneg=''):
    """Convert Decimal to a money formatted string.

    places:  required number of places after the decimal point
    curr:    optional currency symbol before the sign (may be blank)
    sep:     optional grouping separator (comma, period, space, or blank)
    dp:      decimal point indicator (comma or period)
             only specify as blank when places is zero
    pos:     optional sign for positive numbers: '+', space or blank
    neg:     optional sign for negative numbers: '-', '(', space or blank
    trailneg:optional trailing minus indicator:  '-', ')', space or blank

    >>> d = Decimal('-1234567.8901')
    >>> moneyfmt(d, curr='$')
    '-$1,234,567.89'
    >>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
    '1.234.568-'
    >>> moneyfmt(d, curr='$', neg='(', trailneg=')')
    '($1,234,567.89)'
    >>> moneyfmt(Decimal(123456789), sep=' ')
    '123 456 789.00'
    >>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
    '<0.02>'
    ATTENZIONE: se si vuole che arrotondi si deve mettere
    un due ma si deve dare anche
    un valore con soli due decimali
    """
#    qq = Decimal(10) ** -places      # 2 places --> '0.01'
    precisione = int(decimal) or int(setconf(key="decimals", section="Numbers"))
    sign, digits, exp = Decimal(value).as_tuple()
    result = []
    digits = map(str, digits)
    build, next = result.append, digits.pop
    if sign:
        build(trailneg)
    for i in range(precisione):
        build(next() if digits else '0')
    if not precisione:
        build("0")
        build("0")
    build(dp)
    if not digits:
        build('0')
    i = 0
    while digits:
        build(next())
        i += 1
        if i == 3 and digits:
            i = 0
            build(sep)
    build(curr)
    build(neg if sign else pos)
    return ''.join(reversed(result))


def deaccenta(riga=None):
    """ questa funzione elimina gli accenti magari non graditi in alcuni casi"""
    nkfd_form = unicodedata.normalize('NFKD', unicode(riga))
    only_ascii = nkfd_form.encode('ASCII', 'ignore')
    return only_ascii

def setconf(section, key, value=False):
    """ Importante funzione che "semplifica" la lettura dei dati dalla tabella
    di configurazione setconf
    Tentativo abbastanza rudimentale per gestire le liste attraverso i ; ma
    forse si potrebbero gestire più semplicemente con le virgole
    """
    try:
        if Environment.tipo_eng =="postgresql" or Environment.tipo_eng =="postgres" :
            if not hasattr(Environment.conf, "Documenti"):
                Environment.conf.add_section("Documenti")
                Environment.conf.save()
            if  hasattr(Environment.conf, "Documenti") and not hasattr(Environment.conf.Documenti, "cartella_predefinita"):
                setattr(Environment.conf.Documenti,"cartella_predefinita",Environment.documentsDir)
                Environment.conf.save()
            if key == "cartella_predefinita":
                return Environment.conf.Documenti.cartella_predefinita
    except:
        pass
    from promogest.dao.Setconf import SetConf
    #confList = Environment.confList
    if not Environment.confDict:
        confList = SetConf().select(batchSize=None)
        for d in confList:
            Environment.confDict[(d.key,d.section)] = d
    if (key,section) not in Environment.confDict:
        if not value:
            confff = SetConf().select(key=key, section=section)
        elif value:
            confff = SetConf().select(key=key, section=section, value=value)
        if confff:
            confff = confff[0]
    else:
        confff = Environment.confDict[(key, section)]
    c = []
    if confff:
        valore = confff.value
        if valore and ";" in valore:
            val = valore.split(";")
            for a in val:
                c.append(a.strip())
            return c
        else:
            if valore == "":
                return None
            elif valore and len(valore.split("/"))>=2:
                return valore
            else:
                try:
                    return eval(valore)
                except:
                    return valore

    else:
        return ""

def number_format():
    stringa = str('%-14.'+ str(setconf("Numbers", "decimals")) +'f')
    return stringa

def orda(name):
     a = "".join([str(ord(x)) for x in list(name)])
     return a

def last_day_of_month(y, m):
    """Return day (1..31) which is last day of month m in year y
    """
    _dim = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

    def _leap(y):
        if y % 4: return 0
        if y % 100: return 1
        if y % 400: return 0
        return 1

    if m == 2:
        return 28 + _leap(y)
    if not (1 <= m <= 12):
        raise Exception("month not in 1..12")
    return _dim[m]

def date_range(start, end):
    r = (end+datetime.timedelta(days=1)-start).days
    return [start+datetime.timedelta(days=i) for i in range(r)]

def dateToOrdinal(anno):
    dd = []
    ccccc = Calendar().yeardatescalendar(int(Environment.workingYear))
    for cccc in ccccc:
        for ccc in cccc:
            for cc in ccc:
                for c in cc:
                    if c.year==int(Environment.workingYear) and c.toordinal() not in dd:
                        dd.append((c.toordinal(),c))
    return dd


def format_sec(sec):
    sec = int(sec)
    min_, sec = divmod(sec, 60)
    hour, min_ = divmod(min_, 60)
    return '%d:%02d:%02d' % (hour, min_, sec)


def b(stringa):
    if stringa:
        stringa = "<b>"+stringa+"</b>"
    else:
        stringa = ""
    return stringa


def c(stringa, color):
    if stringa:
        stringa = "<span foreground='%s' >" %(color) +stringa+"</span>"
    else:
        stringa = ""
    return stringa

def scribusVersion(slafile):
    Environment.pg2log.info( "QUESTO E' IL FILE SLA"+ slafile)
    try:
        doc = ET.parse(slafile)
    except:
        Environment.new_print_enjine=False
        return False
    root = doc.getroot()
    doc.findall('DOCUMENT')[0]
    slaversion = root.get('Version')
    Environment.pg2log.info( "FILE SLA DA VERIFICARE PRIMA DLLA STAMPA "+ slafile)
    Environment.pg2log.info("VERSIONE SLA  "+ str(slaversion))
    if slaversion in ("1.3.6", "1.3.7", "1.3.8", "1.3.9","1.4.0","1.4.0.rc2","1.4.0.rc3","1.4.0.rc4"):
        Environment.new_print_enjine=True
        return True
    elif slaversion in ("1.3.5.1", "1.3.5svn"):
#        messageInfo(msg="ATTENZIONE FORMATO TEMPLATE SLA DA CORREGGERE 1.3.5")
        print "ATTENZIONE FORMATO TEMPLATE SLA DA CORREGGERE 1.3.5"
        Environment.pg2log.info("ATTENZIONE FORMATO TEMPLATE SLA DA CORREGGERE 1.3.5")
        return True
    elif "1.3.4" in slaversion:
        Environment.new_print_enjine=False
        return False
    elif slaversion in ("1.3.3","1.3.3.6cvs"):
        Environment.new_print_enjine = False
        return False
    else:
        Environment.new_print_enjine=True
        return True


def fencemsg():
    msg = """OPERAZIONE NON CONSENTITA CON IL PACCHETTO
CHE STAI USANDO, PASSA ALLA "ONE STANDARD"
O ALLA "ONE FULL" OPPURE ACQUISTA IL MODULO
DI CUI HAI BISOGNO
   GRAZIE"""
    return messageInfo(msg=msg)

#@timeit
def prep2json(daos):
    if type(daos) == type([]):
        return [d.jsanity() for d in daos]
    else:
        return daos.jsanity()

def resolve_save_file_path():
    """ risoluzione della cartella di salvataggio del file e del nome del file """
    cartella = setconf("General", "cartella_predefinita") or ""
    if cartella == '':
        if os.name == 'posix':
            cartella = os.environ['HOME']
        elif os.name == 'nt':
            cartella = os.environ['USERPROFILE']
    return os.path.join(cartella, "documenti_" + time.strftime('%d_%m_%Y') + '.pdf')


class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, Decimal):
            return float(o)
        super(DecimalEncoder, self).default(o)

def generateRandomBarCode(ean=13):
    """
    funzione di generazione codice ean13 random
    utile per quei prodotti che non hanno un codice
    chiaramente solo per uso interno
    """
    import random
    #from promogest.dao.CodiceABarreArticolo import CodiceABarreArticolo
    codice = ''

    def create(ean):
        """
        crea un codice di tipo ean
        @param ean: tipo di codice ( al momento gestisce  ean8 ed ean13)
        @type ean: int ( 8 o 13 )
        """
        code = [2, 0]
        if ean == 13:
            for a in xrange(10):
                code.append(random.sample(
                                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], 1)[0])
            dispari = (code[1] \
                        + code[3] \
                        + code[5] \
                        + code[7] \
                        + code[9] \
                        + code[11]) * 3
            pari = code[0] + code[2] + code[4] + code[6] + code[8] + code[10]
        elif ean == 8:
            for a in xrange(5):
                code.append(random.sample(
                                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], 1)[0])
            dispari = (code[0] + code[2] + code[4] + code[6]) * 3
            pari = code[1] + code[3] + code[5]
        tot = 10 - ((dispari + pari) % 10)
        if tot == 10:
            tot = 0
        code.append(tot)
        b = ''
        for d in code:
            b = b + str(d)
        return b
    correct = False
    while correct is False:
        codice = create(ean)
        there = None #CodiceABarreArticolo().select(codice=codice)
        if not there:
            return codice
        else:
            create(ean)


def createbarcode(ch, w=7, h=3, dpi=150,tipo="Code128", filename="bcode.png"):
    """ 'Code128','QR' """
    bcd = createBarcodeDrawing(tipo, value=ch, width=float(w)*cm,height=float(h)*cm,humanReadable=True)
    #bcc = createBarcodeImageInMemory(tipo, value=ch, width=float(w)*cm,height=float(h)*cm,humanReadable=True,format='png', dpi=200)
    a = renderPM.drawToFile(bcd, filename, 'PNG', dpi=dpi)
    f = open(filename, "rb")
    #f.read()
    #f.close()
    os.remove(filename)
    return f.read()

def createbarcodeqr(ch, w=5, h=5, dpi=150,tipo="QR", filename="bcodeqr.png"):
    """ 'Code128','QR' """
    bcd = createBarcodeDrawing(tipo, value=ch, width=float(w)*cm,height=float(h)*cm)
    #bcc = createBarcodeImageInMemory(tipo, value=ch, width=float(w)*cm,height=float(h)*cm, dpi=dpi)
    a = renderPM.drawToFile(bcd, filename, 'PNG', dpi=dpi)
    f = open(filename, "rb")
    os.remove(filename)
    return f.read()
